//Justin Ball
//James Sundstrom
//CS442

#include <iostream>		
#include <stdio.h>	
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <list>
#include <deque>
#include <openssl/md5.h>
#include <fcntl.h>
#include "SendTo.h"
#include "myPacket.h"

using namespace std;

const int BUFSIZE=1024;

struct sockaddr_in to; //Make this struct global. It shouldn't change for the client.

std::deque<myPacket> windowlist;

int sock; //Global to hold the socket descriptor.

//Socket set up.
int MakeUdpSocket(char *hostname, char *port, struct sockaddr_in *to) {
	struct hostent *hp;
	int portnum;
	int sock;
	hp = gethostbyname(hostname);
	bcopy((char *)hp->h_addr, (char *)&to->sin_addr, hp->h_length);
	to->sin_family = AF_INET;
	sscanf(port, "%d", &portnum);
	to->sin_port = htons(portnum);
	sock = socket(AF_INET, SOCK_DGRAM, 0);
	cout << "CLIENT:  Made socket host: " << hostname << " port: " << port << endl;
	if (sock < 0) {
		perror("MakeUdpSocket");
		exit(1);
	}
	return sock;
}

/* Given a bitmap of recieved/acked packets, decides where the window should go next. */
int advanceWindow(int bitmap[], int num_packets, int window_pos, int window_size) {
	
	if((window_pos == num_packets - 1) && (bitmap[window_pos] == 1)) {
		return window_pos + 1;
	}
	
	int i;
	for (i = window_pos; i < num_packets; i++) {
		if(bitmap[i] == 0)
			return i;
	}
	return i;
}

void AddrToString(char *string, struct sockaddr_in addr) {
	unsigned char *bytes = (unsigned char *)&addr.sin_addr; 
	sprintf(string,
		"Address: %d.%d.%d.%d port %d",
		bytes[0],
		bytes[1],
		bytes[2],
		bytes[3],
		ntohs(addr.sin_port));
}

//Send a packet.
void sendPacket(myPacket packet) {

    uint8_t pookie[16];

    //Add the checksum to the packet.
    computeMD5(&packet, pookie);
    memcpy(packet.checksum, pookie, 16);

    //Call the packet dropping SendTo function.
    int ret = SendTo(sock, &packet, MY_PACKET_SIZE, 0, (sockaddr *)&to, sizeof(to));

    //Do some error checking.
    if(ret == -1) {
	perror("Send packet failed");
	exit(1);
    }

    return;
}

//Handles the initial handshake with the server. Returns the total number of packets
//that will be transferred.
int initConnection(myPacket* packet) {
    
    //Create structs for select.
    fd_set fds;
    struct timeval tv;

    //Buffer to hold data from the socket.
    char buf[MY_PACKET_SIZE];

    //Struct to fill in from recvfrom()
    struct sockaddr_in from; 
    unsigned int from_len;

    int total_packets = -1;

    while(1) {

	//Set the timeout to x microseconds.
	tv.tv_sec = 0;
	tv.tv_usec = 500000;

	//Add the sock FD to the fd set for select.
	FD_ZERO(&fds);
	FD_SET(sock, &fds);

	//Send off the initial handshake packet to the server.
	sendPacket(*packet);

	//Wait for a response or timeout.
	int ret = select(sock + 1, &fds, 0, 0, &tv);
	if (ret == -1) {

	    //Error check select.
	    perror("Select failure:");
	    exit(1);

	} else if (FD_ISSET(sock, &fds)) {

	    //Get the ready packet from the socket.
	    int len = recvfrom(sock, buf, MY_PACKET_SIZE, 0, (sockaddr *)&from, &from_len);	

	    //Cast the buffer into a myPacket struct.
	    myPacket *recv_packet = (myPacket *)buf;
	    uint8_t flags = recv_packet->flags;

	    //Check the packet for corruption.
	    if( !checkMD5(recv_packet) ) {

		cout << "Handshake ack is corrupt." << endl;
	    } else if ( (flags & ACK) && (((flags & INIT) >> 7) || (flags & DIR_REQ) >> 4))  {

		total_packets = (int)recv_packet->num_packets;
		cout << "Handshake complete." << endl;
		break;
	    } else {
		//This is not relevant, drop it.
	    }

	} else {
	    //timeout.
	    cout << "Handshake timeout. Resending..." << endl;
	}
    }

    return total_packets;

}

void sendFile(char* filename) {


    myPacket initPacket;
    struct stat st;
    stat(filename, &st);
    int filesize = st.st_size;
    int total_packets = numberOfPackets(filename);
    
    cout << "sendin a file: " << filesize << "bytes." << endl;


    bzero(&initPacket, sizeof(initPacket));
    
    //Put the length of the filename in the payload size field.
    initPacket.pl_size = strlen(filename);

    //Put the filename in the payload
    strcpy((char *)initPacket.payload, filename);
    
    //Turn on INIT and SEND to indicate to the server that we want to send it a file.
    initPacket.flags = INIT | SEND;
    
    initPacket.num_packets = total_packets;
    
    total_packets = initConnection(&initPacket);
    
       //Struct
    packet_entry* packet_list;

    //Create structs for select.
    fd_set fds;
    struct timeval tv;

    //Struct to fill in from recvfrom()
    struct sockaddr_in from; 
    unsigned int from_len;


    packet_list = new packet_entry[total_packets];
    bzero(packet_list, total_packets * sizeof(struct packet_entry));
	

    //Start the sliding window at 0.
    int window_pos = 0;
    int window_size = calculateWindowSize(total_packets);
	
    //Bitmap that keeps track of acks recieved.
    int * bitmap;
	bitmap = (int *)malloc(total_packets * sizeof(int));
    bzero(bitmap, total_packets * sizeof(int));

    //Buffer to hold data from the socket.
    char buf[MY_PACKET_SIZE];

    //Open the file for reading.
    int file_fd = open(filename, O_RDONLY);
    if (file_fd == -1) {
	perror("File open went wrong");
	exit(1);
    }


    //Selective repeat (send side).
    while(1) {

	for(int i = window_pos; ((i < window_size + window_pos) && (i < total_packets)); i++) {
	
	usleep(50);
	
		if (bitmap[i] == 0) {

            //cout << "WASSENT is this before if: " << packet_list[i].wasSent << endl;
		    //Check if the packet is in the deque.
			if ( packet_list[i].wasSent == 1) {
            
				//check timeout
				timeval stamp, current;
				long elapsed_sec, elapsed_usec, elapsed_msec;

				packet_entry entry = packet_list[i];
				stamp = entry.timestamp;
				gettimeofday(&current, 0);

				//Calculate the time differential between the stamp and the current time.
				elapsed_sec  = current.tv_sec  - stamp.tv_sec;
				elapsed_usec = current.tv_usec - stamp.tv_usec;

				//Get the total time elapsed in milliseconds.
				elapsed_msec = ((elapsed_sec) * 1000 + elapsed_usec/1000.0) + 0.5;

				//cout << "Elapsed time: " << elapsed_msec << endl;
				
				//Check for a timeout.
				if( elapsed_msec > TIMEOUT) {

				    //Resend the timedout packet.
				    cout << "Packet " << i << " timed out. Resending...." << endl;
                    cout << "Packet " << i << " has resent? " << packet_list[i].resends << endl;
				    sendPacket(entry.packet);
				    packet_list[i].timestamp = current;
                    packet_list[i].resends = packet_list[i].resends + 1;
                    
                    if(packet_list[i].resends >= MAX_RESENDS) {
                        bitmap[i] = 1;
                       
                    }
                  
				}
			}
		
			else {
				
				//Send the packet.
				myPacket send_packet = generatePacket(file_fd, i,total_packets, SEND);

				//cout << "Sending packet (seq number) " << i << " of " << total_packets << endl;

				//Send the packet off.
				sendPacket(send_packet);
				
				packet_entry list_entry;

				//Generate a timestamp;
				timeval start;
				gettimeofday(&start, 0);

				//Fill list entry struct with the packet, timestamp, and sequence num.
				list_entry.packet = send_packet;
				list_entry.timestamp = start;
                list_entry.wasSent = 1;
                list_entry.resends = 0;

				//Add to deque with a timestamp.
				packet_list[i] = list_entry;
				
			}
           
		}
		//Don't do anything if the bitmap[i]=1. That means we got an ACK.
	}
	
    while(bitmap[window_pos] == 1) {
        window_pos ++;
        if(window_pos >= total_packets)
            break;
    }
	
    cout << "Window pos is " << window_pos << " of " << total_packets << endl;
    cout << "Bitmap here is: " << bitmap[window_pos] << endl;

    //Quit if the bitmap is all 1's
    if(window_pos == (total_packets)) {
        break;
    }
    
	//Set the timeout to x microseconds.
	tv.tv_sec = 0;
	tv.tv_usec = 50000;

	//Add the sock FD to the fd set for select.
	FD_ZERO(&fds);
	FD_SET(sock, &fds);

	//Wait for a response or timeout.
	int ret = select(sock + 1, &fds, 0, 0, &tv);
	if (ret == -1) {

	    //Error check select.
	    perror("Select failure:");
	    exit(1);

	} else if (FD_ISSET(sock, &fds)) {

	    //Get the ready packet from the socket.
	    int len = recvfrom(sock, buf, MY_PACKET_SIZE, 0, (sockaddr *)&from, &from_len);

	    //Cast the buffer into a myPacket struct.
	    myPacket *recv_packet = (myPacket *)buf;

	    //Get some info from the packet.
	    int sequence_num = (int)recv_packet->seq_num;
	    int payload_size = (int)recv_packet->pl_size;
	    int flags = (int)recv_packet->flags;

	    if( !checkMD5(recv_packet) ) {
            cout << "Ack packet " << sequence_num << " is corrupt. Dropping it..." << endl;
	    } else if( ((flags & INIT) >> 7) ) {

            //Resend the ack for the init handshake because it must have gotten lost.
            //Copy the incoming and flip on the ACK bit to make an ack packet to send back.
            myPacket ack = *recv_packet;
            ack.flags = (ack.flags | ACK);
            sendPacket(ack);

	    } else if ( (sequence_num >= window_pos) && (sequence_num < (window_pos + window_size))) {

            //Received an ack in the window.
            bitmap[sequence_num] = 1;

            cout << "Received ack: " << sequence_num  << endl;

            //Remove from deque.
            //removeFromDeque(sequence_num);

            //Advance the window.
            //window_pos = advanceWindow(bitmap, total_packets, window_pos, window_size);
            while(bitmap[window_pos] == 1) {
                window_pos ++;
                if(window_pos >= total_packets)
                    break;
            }
            

	    } else {
            //Late acknowledgement. Do nothing.

	    }

	    if( window_pos >= total_packets - 1) {
            //Transfer is done.
            break;
	    }


	} else {
	    //timeout.
	}
    }

    cout << "Transfer complete." << endl;

    // That's what you get for acting the mickey.
    delete [] packet_list;

    close(file_fd);
    return;
}

void getFile(char* filename) {

    myPacket initPacket;
    bzero(&initPacket, sizeof(initPacket));

    //Put the length of the filename in the payload size field.
    initPacket.pl_size = strlen(filename);

    //Put the filename in the payload
    strcpy((char *)initPacket.payload, filename);

    //Turn on INIT and RECV to indicate to the server we want a file.
    initPacket.flags = 0xC0;

    //Do a handshake with the client to set up a transfer and get the total number of packets.
    int total_packets = initConnection(&initPacket);

    //LOL
    myPacket* packet_list = new myPacket[total_packets];
    bzero(packet_list, total_packets * sizeof(struct myPacket));

    cout << total_packets << endl;
	
	//Bitmap that keeps track of packets recieved.
    int * bitmap;
	bitmap = (int *)malloc(total_packets * sizeof(int));
    bzero(bitmap, total_packets * sizeof(int));

    //Open the file. Create it if it doesn't exist or destroy it if it already does.
    int file_fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0777);
    if (file_fd == -1) {
	perror("File open went wrong");
	exit(1);
    }

    //Start the sliding window at 0.
    int window_pos = 0;
    int window_size = calculateWindowSize(total_packets);

    //Struct to fill in from recvfrom()
    struct sockaddr_in from; 
    unsigned int from_len;

    //Buffer to hold data from the socket.
    char buf[MY_PACKET_SIZE];

    //Selective repeat (receive side).
    while(1) {

        int len = recvfrom(sock, buf, MY_PACKET_SIZE, 0, (sockaddr *)&from, &from_len);

        //Cast the buffer into a myPacket struct.
        myPacket *recv_packet = (myPacket *)buf;

        int sequence_num = (int)recv_packet->seq_num;
        int payload_size = (int)recv_packet->pl_size;

        //Check the packet for corruption.
        if( !checkMD5(recv_packet) ) {

            cout << "Packet " << sequence_num << " is corrupt. Dropping it..." << endl;
        } else if ( (sequence_num >= window_pos) && (sequence_num < (window_pos + window_size))) {


            //ACK
            myPacket ack = *recv_packet;
            ack.flags = (ack.flags | ACK);
           
            sendPacket(ack);
     
            //Set the corresponding bit to 1 in the bitmap.
            bitmap[sequence_num] = 1;

	    packet_list[sequence_num] = *recv_packet;
            
            cout << "Window pos before: " << window_pos << endl;
            
            //Move window up to the next empty slot.
            //window_pos = advanceWindow(bitmap, total_packets, window_pos, window_size);
            
            while(bitmap[window_pos] == 1) {
                window_pos ++;
                if(window_pos >= total_packets)
                    break;
            }
            cout << "Window pos after: " << window_pos << endl;
            
        } else {
        
            

            //Send an ACK for out of sequence packet and drop it.
            myPacket ack = *recv_packet;
            ack.flags = (ack.flags | ACK);
           
            sendPacket(ack);

        }	    

        //Check if the window is at the end.
        if (window_pos >= total_packets) {

            cout << "Transfer is complete! Window pos is " << window_pos << " total packets is" << total_packets << endl;
            //File transfer is done.
            break;
        }
	

    }

    cout << "Writing to file" << endl;

    for(int i = 0; i < total_packets; i++) {

	int write_ret = write(file_fd, packet_list[i].payload, packet_list[i].pl_size);
        if( write_ret <= 0) {
	    perror("Write failed:");
	    exit(1);
	}



    }

    close(file_fd);
    
    return;

}

void getDir() {

    myPacket initPacket;
    bzero(&initPacket, sizeof(initPacket));

    //Struct to fill in from recvfrom()
    struct sockaddr_in from; 
    unsigned int from_len;

    //Turn on DIR_REQ to indicate to the server we want a dir.
    initPacket.flags = DIR_REQ;

    int total_packets = initConnection(&initPacket);

    char dirbuffer[5000];
    char buf[MY_PACKET_SIZE];
    strcpy(dirbuffer, "\nThe server's directory listing:\n");

    int current_pos = 0;

    //Loop until all packets have been received.
    while ( current_pos < total_packets ) {

	//Get the ready packet from the socket.
	int len = recvfrom(sock, buf, MY_PACKET_SIZE, 0, (sockaddr *)&from, &from_len);	

	//Cast the buffer into a myPacket struct.
	myPacket *recv_packet = (myPacket *)buf;
	uint8_t flags = recv_packet->flags;

	int sequence_num = recv_packet->seq_num;

	if ( sequence_num == current_pos) {

	   strncat(dirbuffer, (char *)recv_packet->payload, recv_packet->pl_size);
	   current_pos++;  

	}
    
	//Copy the incoming and flip on the ACK bit to make an ack packet to send back.
	myPacket ack = *recv_packet;
	ack.flags = (ack.flags | ACK);
	sendPacket(ack);


    }

    //Print the directory listing.
    cout << dirbuffer << endl;

}


int main(int argc, char *argv[]) {

	int len;	
	struct servent *sp;
	char buf[BUFSIZE];

	//Hard code the hostname and port number.
	char hostname[] = "euclid";
	char port[] = "13722";

	//Set up the UDP socket.
	sock = MakeUdpSocket(hostname, port, &to);
	cout << "CLIENT:  Made socket # " << sock << endl;
	AddrToString(buf, to);
	cout << "CLIENT:  Connected to " << buf << endl;

	//Make sure the user entered some arguments.
	if (argc < 2) {
	    cout << "Enter some arguments." << endl;
	    exit(1);
	}

	//The command line interface baby.

	//Handle a directory listing command.
	if ( strcmp(argv[1], "dir") == 0 ) {
	    
	    getDir();

	//Handle a get file command.
	} else if ( strcmp(argv[1], "get") == 0) {

	    //Check that a filename was entered.
	    if ( argc != 3) {
		cout << "Enter a filename jackass." << endl;
		exit(1);
	    }

	    //Go to getFile to start the transfer process.
	    getFile(argv[2]);
	    
	//Handle a send file command.
	} else if( strcmp(argv[1], "send") == 0) {

	    //Check that a filename was entered.
	    if ( argc != 3) {
		cout << "Enter a filename jackass." << endl;
		exit(1);
	    }

	    //Go to sendFile to start the transfer process.
	    sendFile(argv[2]);

	} else {
	    
	    //Default case to handle bogus input.
	    cout << "Invalid arguments" << endl;
	    exit(1);
	}

	close(sock);

	return 0;
}

